package gameoflife.automaton;

import gameoflife.Heading;
import gameoflife.Pattern;
import gameoflife.Position;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class HashAutomaton extends AbstractAutomaton {

	private static class Cell extends gameoflife.Cell {
		int peers = 0;

		Cell(Position position) {
			super(position);
		}
	}

	private final Map<Position, Cell> map = new HashMap<Position, Cell>();

	private final HashSet<Position> dead = new HashSet<Position>();
	private final HashMap<Position, Cell> born = new HashMap<Position, Cell>();

	private int births = 0;
	private int deaths = 0;

	private final ReadWriteLock lock = new ReentrantReadWriteLock();
	private final Lock mapRead = lock.readLock();
	private final Lock mapWrite = lock.writeLock();

	protected void step() {
		generation++;
		dead.clear();
		born.clear();

		final Position neighborPosition = new Position();
		for (Cell cell : map.values()) {
			cell.age();
			int peers = 0;

			for (Heading heading : Heading.ALL) {
				heading.toPosition(cell, neighborPosition);

				if (map.containsKey(neighborPosition)) {
					peers++;
				}
				else {
					Cell candidate = born.get(neighborPosition);
					if (candidate == null) {
						candidate = new Cell(neighborPosition);
						born.put(candidate, candidate);
					}
					candidate.peers++;
				}
			}
			if (!rule.survives(peers)) {
				dead.add(cell);
			}
		}

		mapWrite.lock();
		{
			deaths = dead.size();
			for (Position position : dead) {
				map.remove(position);
			}
			births = 0;
			for (Cell candidate : born.values()) {
				if (rule.born(candidate.peers)) {
					map.put(candidate, candidate);
					births++;
				}
			}
		}
		mapWrite.unlock();
	}

	@Override
	public void accept(Visitor visitor) {
		mapRead.lock();
		try {
			for (Cell cell : map.values()) {
				visitor.visit(cell);
			}
		}
		finally {
			mapRead.unlock();
		}
	}

	public synchronized void set(Position position, boolean value) {
		mapWrite.lock();
		if (value) {
			map.put(position, new Cell(position));
		}
		else {
			map.remove(position);
		}
		mapWrite.unlock();

		stateChanged(0);
	}

	public synchronized void clear() {
		mapWrite.lock();
		map.clear();
		births = 0;
		deaths = 0;
		generation = 0;
		mapWrite.unlock();

		stateChanged(0);
	}

	public synchronized void addAll(Collection<Position> positions) {
		mapWrite.lock();
		for (Position position : positions) {
			map.put(position, new Cell(position));
		}
		mapWrite.unlock();

		stateChanged(0);
	}

	public synchronized boolean isSet(Position position) {
		return map.containsKey(position);
	}

	public synchronized Cell getCell(Position position) {
		return map.get(position);
	}

	public synchronized int size() {
		return map.size();
	}

	public synchronized int births() {
		return births;
	}

	public synchronized int deaths() {
		return deaths;
	}

	@Override
	public synchronized Pattern toPattern() {
		Collection<Position> positions = new ArrayList<Position>(map.values());
		return new Pattern(getRule(), positions);
	}
}
